﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading;
using ProjectLib;

namespace Client
{
    [Serializable]
    public class ClientConnectionManager
    {
        private static ClientConnectionManager instance;
        private TcpClient channel;          // channel
        private NetworkStream stream;       // network stream
        private BinaryFormatter formatter;  // formater
        private Thread listenerThread;      // litener thread 
        private bool isConnected;

        public static ClientConnectionManager Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = new ClientConnectionManager();
                }
                return instance;
            }
        }

        public ClientConnectionManager()
        {
            formatter = new BinaryFormatter();
            isConnected = false;
        }

        public void ConnectToServer()
        {
            try
            {
                channel = new TcpClient();
                channel.Connect(ProjectSettings.Default.Hostname, ProjectSettings.Default.Port);
                stream = channel.GetStream();
                if (stream.CanWrite && stream.CanRead)
                {
                    isConnected = true;
                }
            }
            catch (System.ArgumentNullException)
            {
                DisconnectFromServer();
                throw (new ConnectionException("Failed to connect to server, the hostname parameter is null"));
            }
            catch (System.ArgumentOutOfRangeException)
            {
                DisconnectFromServer();
                throw (new ConnectionException("Failed to connect to server, the port parameter is not valid"));
            }
            catch (System.Net.Sockets.SocketException)
            {
                DisconnectFromServer();
                throw (new ConnectionException("Failed to connect to server, an error occurred when accessing the socket"));
            }
            catch (System.ObjectDisposedException)
            {
                DisconnectFromServer();
                throw (new ConnectionException("Failed to connect to server, TcpClient is close"));
            }
            catch (System.InvalidOperationException)
            {
                DisconnectFromServer();
                throw (new ConnectionException("Failed to connect to server, TcpClient is not connected to a remote host"));
            }
        }

        private void DisconnectFromServer()
        {
            isConnected = false;
            if (channel.Connected)
            {
                stream.Close();
                channel.Close();
            }
        }

        /// <summary>
        /// Start theard listner
        /// </summary>
        public void StartToListen()
        {
            if (isConnected.Equals(true))
            {
                listenerThread = new Thread(this.ListenToServer);
                listenerThread.Start();
            }
        }

        /// <summary>
        /// Send data to server
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public void SendData(object data)
        {
            try
            {
                lock (this)
                {
                    // encoder the data and send it
                    formatter.Serialize(stream, data);
                }
            }
            catch (System.ArgumentNullException)
            {
                throw (new ConnectionException("Failed to send data server, check if server running"));
            }
            catch (System.ArgumentException)
            {
                throw (new ConnectionException("Failed to send data server, check if server running"));
            }
            catch (System.Runtime.Serialization.SerializationException)
            {
                throw (new ConnectionException("Failed to send data server, check if server running"));
            }
            catch (System.Security.SecurityException)
            {
                throw (new ConnectionException("Failed to send data server, check if server running"));
            }
        }

        /// <summary>
        /// Stop listener - close channel and stream
        /// </summary>
        public void StopToListen()
        {
            isConnected = false;
            stream.Close();
            channel.Close();
        }


        /// <summary>
        /// Listen to server and handle the data
        /// </summary>
        private void ListenToServer()
        {
            // work while the channel connected
            while (channel.Connected)
            {
                try
                {
                    // decoder the data
                    object data = formatter.Deserialize(stream);
                    // handle the data
                    ClientManager.Instance.HandleReadyRequest(data);
                }
                catch (System.IO.IOException)
                {
                    stream.Close();
                    channel.Close();
                }
            }
            // check if client closed the connection
            if (!isConnected)
            {
                StopToListen();
            }
        }
    }
}
